﻿using System;
using System.DirectoryServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ActiveDirectoryUtilities.CheckDsAcls
{
	public enum AceRelationToReferenceSecurityDescriptor
	{
		Included,
		Additional,
		Missing,
		NotApplicable,
		Unknown
	};

	public class AnalyzableAce
	{
		AuthorizationRule _ace;
		AceRelationToReferenceSecurityDescriptor _knownRelationship = AceRelationToReferenceSecurityDescriptor.Unknown;
		bool _areRulesProtected, _areRulesCanonical;

		#region c'tors
		public AnalyzableAce(AuthorizationRule ace, bool areRulesProtected, bool areRulesCanonical)
		{
			_ace = ace;
			_areRulesProtected = areRulesProtected;
			_areRulesCanonical = areRulesCanonical;
		}

		public AnalyzableAce(ActiveDirectoryAccessRule ace, bool areRulesProtected, bool areRulesCanonical)
		{
			_ace = ace;
			_areRulesProtected = areRulesProtected;
			_areRulesCanonical = areRulesCanonical;
		}

		public AnalyzableAce(ActiveDirectoryAuditRule ace, bool areRulesProtected, bool areRulesCanonical)
		{
			_ace = ace;
			_areRulesProtected = areRulesProtected;
			_areRulesCanonical = areRulesCanonical;
		}
		#endregion

		#region Properties
		public bool AreRulesProtected { get { return _areRulesProtected; } }
		public bool AreRulesCanonical { get { return _areRulesCanonical; } }

		public string AceType
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return "DACL";
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return "SACL";
				}
				return string.Empty;
			}
		}

		/// <summary>
		/// String representation of access type
		/// </summary>
		public string AccessControlType
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).AccessControlType.ToString();
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).AuditFlags.ToString();
				}
				return string.Empty;
			}
		}

		public string ActiveDirectoryRights
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).ActiveDirectoryRights.ToString();
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).ActiveDirectoryRights.ToString();
				}
				return string.Empty;
			}
		}

		public IdentityReference IdentityReference { get { return _ace.IdentityReference; } }

		public InheritanceFlags InheritanceFlags { get { return _ace.InheritanceFlags; } }

		public string InheritanceType
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).InheritanceType.ToString();
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).InheritanceType.ToString();
				}
				return string.Empty;
			}
		}

		public Guid InheritedObjectType
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).InheritedObjectType;
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).InheritedObjectType;
				}
				return Guid.Empty;
			}
		}

		public bool IsInherited
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).IsInherited;
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).IsInherited;
				}
				throw new ApplicationException("Security Descriptor must be of type ActiveDirectoryAccessRule or ActiveDirectoryAuditRule");
			}
		}

		public string ObjectFlags
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).ObjectFlags.ToString();
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).ObjectFlags.ToString();
				}
				return string.Empty;
			}
		}

		public Guid ObjectType
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).ObjectType;
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).ObjectType;
				}
				return Guid.Empty;
			}
		}

		public string PropagationFlags
		{
			get
			{
				if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
				{
					return ((ActiveDirectoryAccessRule)_ace).PropagationFlags.ToString();
				}
				else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
				{
					return ((ActiveDirectoryAuditRule)_ace).PropagationFlags.ToString();
				}
				return string.Empty;
			}
		}

		public AceRelationToReferenceSecurityDescriptor IsAceInSecurityDescriptor { get { return _knownRelationship; } }
		#endregion

		#region Methods
		internal void LookForAceInSecurityDescriptor(byte[] securityDescriptor, IdentityReference ownerAccount)
		{
			LookForAceInSecurityDescriptor(securityDescriptor, ownerAccount, false);
		}
		internal void LookForAceInSecurityDescriptor(byte[] securityDescriptor, IdentityReference ownerAccount, bool checkForMissingAce)
		{
			if (securityDescriptor == null) { return; }
			ActiveDirectorySecurity ads = new ActiveDirectorySecurity();
			ads.SetSecurityDescriptorBinaryForm(securityDescriptor);
			LookForAceInSecurityDescriptor(ads, ownerAccount);
		}

		internal void LookForAceInSecurityDescriptor(string securityDescriptor, IdentityReference ownerAccount)
		{
			LookForAceInSecurityDescriptor(securityDescriptor, ownerAccount, false);
		}
		internal void LookForAceInSecurityDescriptor(string securityDescriptor, IdentityReference ownerAccount, bool checkForMissingAce)
		{
			if (securityDescriptor == null) { return; }
			ActiveDirectorySecurity ads = new ActiveDirectorySecurity();
			ads.SetSecurityDescriptorSddlForm(securityDescriptor);
			LookForAceInSecurityDescriptor(ads, ownerAccount);
		}

		internal void LookForAceInSecurityDescriptor(ActiveDirectorySecurity securityDescriptor, IdentityReference ownerAccount)
		{
			LookForAceInSecurityDescriptor(securityDescriptor, ownerAccount, false);
		}
		internal void LookForAceInSecurityDescriptor(ActiveDirectorySecurity securityDescriptor, IdentityReference ownerAccount, bool checkForMissingAce)
		{
			if (securityDescriptor == null) { return; }

			if (this.IsInherited)
			{
				_knownRelationship = AceRelationToReferenceSecurityDescriptor.NotApplicable;
				return;
			}

			if (_ace.GetType() == typeof(ActiveDirectoryAccessRule))
			{

				foreach (ActiveDirectoryAccessRule comparisonAce in securityDescriptor.GetAccessRules(true, true, typeof(NTAccount)))
				{
					if (areAcesEqual((ActiveDirectoryAccessRule)_ace, comparisonAce, ownerAccount))
					{
						_knownRelationship = AceRelationToReferenceSecurityDescriptor.Included;
						return;
					}
				}
			}
			else if (_ace.GetType() == typeof(ActiveDirectoryAuditRule))
			{
				foreach (ActiveDirectoryAuditRule comparisonAce in securityDescriptor.GetAuditRules(true, true, typeof(NTAccount)))
				{
					if (areAcesEqual((ActiveDirectoryAuditRule)_ace, comparisonAce, ownerAccount))
					{
						_knownRelationship = AceRelationToReferenceSecurityDescriptor.Included;
						return;
					}
				}
			}

			//Additional/Missing implies the "authoritative Ace" (the one supplied in the constructor) does not exist in the reference dacl.
			//	From the user perspective, depending on which list is being compared, the ACL is either missing, or additional.
			//	This is controled by the consumer flagging whether it wants the ACE to be classified as "Missing" or "Additional";
			if (checkForMissingAce)
			{
				_knownRelationship = AceRelationToReferenceSecurityDescriptor.Missing;
				return;
			}

			_knownRelationship = AceRelationToReferenceSecurityDescriptor.Additional;
		}

		private bool areAcesEqual(ActiveDirectoryAccessRule ace1, ActiveDirectoryAccessRule ace2, IdentityReference creatorOwner)
		{
			if (!ace1.AccessControlType.Equals(ace2.AccessControlType)) { return false; }
			if (!ace1.ActiveDirectoryRights.Equals(ace2.ActiveDirectoryRights)) { return false; }
			if (!ace1.InheritanceFlags.Equals(ace2.InheritanceFlags)) { return false; }
			if (!ace1.InheritanceType.Equals(ace2.InheritanceType)) { return false; }
			if (!ace1.InheritedObjectType.Equals(ace2.InheritedObjectType)) { return false; }
			if (!ace1.IsInherited.Equals(ace2.IsInherited)) { return false; }
			if (!ace1.ObjectFlags.Equals(ace2.ObjectFlags)) { return false; }
			if (!ace1.ObjectType.Equals(ace2.ObjectType)) { return false; }
			if (!ace1.PropagationFlags.Equals(ace2.PropagationFlags)) { return false; }

			//Special Case Identity Reference to handle the fact that AD
			//	Automatically translates the CREATOR OWNER default into
			//	the Identity of the CREATOR OWNER
			//	Do this last since it is more complex.
			if (!ace1.IdentityReference.Equals(ace2.IdentityReference))
			{
				if (ace1.IdentityReference.Equals(new NTAccount("CREATOR OWNER")))
				{
					if (!ace2.IdentityReference.Equals(creatorOwner))
					{
						return false;
					}
				}
				else if (ace2.IdentityReference.Equals(new NTAccount("CREATOR OWNER")))
				{
					if (!ace1.IdentityReference.Equals(creatorOwner))
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}

			//All test cases passed.  They are equal.
			return true;
		}

		private bool areAcesEqual(ActiveDirectoryAuditRule ace1, ActiveDirectoryAuditRule ace2, IdentityReference creatorOwner)
		{
			if (!ace1.AuditFlags.Equals(ace2.AuditFlags)) { return false; }
			if (!ace1.ActiveDirectoryRights.Equals(ace2.ActiveDirectoryRights)) { return false; }
			if (!ace1.InheritanceFlags.Equals(ace2.InheritanceFlags)) { return false; }
			if (!ace1.InheritanceType.Equals(ace2.InheritanceType)) { return false; }
			if (!ace1.InheritedObjectType.Equals(ace2.InheritedObjectType)) { return false; }
			if (!ace1.IsInherited.Equals(ace2.IsInherited)) { return false; }
			if (!ace1.ObjectFlags.Equals(ace2.ObjectFlags)) { return false; }
			if (!ace1.ObjectType.Equals(ace2.ObjectType)) { return false; }
			if (!ace1.PropagationFlags.Equals(ace2.PropagationFlags)) { return false; }

			//Special Case Identity Reference to handle the fact that AD
			//	Automatically translates the CREATOR OWNER default into
			//	the Identity of the CREATOR OWNER
			//	Do this last since it is more complex.
			if (!ace1.IdentityReference.Equals(ace2.IdentityReference))
			{
				if (ace1.IdentityReference.Equals(new NTAccount("CREATOR OWNER")))
				{
					if (!ace2.IdentityReference.Equals(creatorOwner))
					{
						return false;
					}
				}
				else if (ace2.IdentityReference.Equals(new NTAccount("CREATOR OWNER")))
				{
					if (!ace1.IdentityReference.Equals(creatorOwner))
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}

			//All test cases passed.  They are equal.
			return true;
		}
		#endregion
	}
}
